package com.binarysoft.service.obj;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.session.SqlSession;

import com.binarysoft.service.id.IDService;
import com.binarysoft.service.obj.dao.ObjectEntity;
import com.binarysoft.service.obj.dao.ObjectEntityDAO;
import com.binarysoft.service.obj.dao.ObjectProperty;
import com.binarysoft.service.obj.dao.ObjectPropertyDAO;
import com.binarysoft.service.obj.dao.ObjectPropertyKey;
import com.binarysoft.service.type.TypeService;

import common.framework.dsb.AbstractServiceBean;
import common.framework.dsb.annotation.DSBService;
import common.framework.dsb.annotation.DSBSql;
import common.framework.dsb.service.ServiceContext;

/**
 * @author David
 * 
 */

@DSBService(name = "DSB/ObjectService", startup = true)
public class ObjectServiceBean extends AbstractServiceBean implements ObjectService {

	private boolean isClosed = false;

	@Override
	public void start(ServiceContext serviceContext) throws Exception {
		super.start(serviceContext);
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public void setObjectActiveStatus(long objID, boolean isActive) throws Exception {

		SqlSession sqlSession = this.getSqlSession();
		ObjectEntityDAO dao = sqlSession.getMapper(ObjectEntityDAO.class);
		ObjectEntity ObjEntity = dao.selectByPrimaryKey(objID);
		ObjEntity.setIsActive(isActive);
		dao.updateByPrimaryKey(ObjEntity);
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public boolean existsObject(long objID) {
		SqlSession sqlSession = this.getSqlSession();
		ObjectEntityDAO dao = sqlSession.getMapper(ObjectEntityDAO.class);
		ObjectEntity ObjEntity = dao.selectByPrimaryKey(objID);
		return ObjEntity != null;
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public Collection<ObjectEntity> getAllObjects() {
		SqlSession sqlSession = this.getSqlSession();
		ObjectEntityDAO dao = sqlSession.getMapper(ObjectEntityDAO.class);
		return dao.selectAll();

	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public Collection<ObjectEntity> getRootObjects() {
		SqlSession sqlSession = this.getSqlSession();
		ObjectEntityDAO dao = sqlSession.getMapper(ObjectEntityDAO.class);
		return dao.selectAllRootObjs();
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public Collection<ObjectEntity> getActivedRootObjects() {
		SqlSession sqlSession = this.getSqlSession();
		ObjectEntityDAO dao = sqlSession.getMapper(ObjectEntityDAO.class);
		return dao.selectAllActiveRootObjs();
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public Collection<Long> getRootObjectIds() {
		SqlSession sqlSession = this.getSqlSession();
		ObjectEntityDAO dao = sqlSession.getMapper(ObjectEntityDAO.class);
		return dao.selectAllRootObjIDs();
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public Map<String, Collection<ObjectEntity>> getTypeGroupedRootObjects() {
		Map<String, Collection<ObjectEntity>> result = new HashMap<String, Collection<ObjectEntity>>();
		Collection<ObjectEntity> entities = getRootObjects();
		for (ObjectEntity ent : entities) {
			Collection<ObjectEntity> mynes = result.get(ent.getObjTypeId());
			if (mynes == null) {
				mynes = new ArrayList<ObjectEntity>();
				result.put(ent.getObjTypeId(), mynes);
			}
			mynes.add(ent);
		}
		return result;
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public Collection<ObjectEntity> getRootObjectsByType(String typeID) throws Exception {
		TypeService typeService = serviceContext.lookup("DSB/TypeService");

		Collection<ObjectEntity> entities = getRootObjects();

		Collection<ObjectEntity> result = new ArrayList<ObjectEntity>();
		for (ObjectEntity entity : entities) {
			if (typeService.isatype(entity.getObjTypeId(), typeID)) {
				result.add(entity);
			}
		}
		return result;
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public Collection<Long> getRootObjectIdsByType(String typeID) throws Exception {
		TypeService typeService = serviceContext.lookup("DSB/TypeService");
		Collection<ObjectEntity> entities = getRootObjects();
		Collection<Long> result = new ArrayList<Long>();
		for (ObjectEntity entity : entities) {
			if (typeService.isatype(entity.getObjTypeId(), typeID)) {
				result.add(entity.getObjId());
			}
		}
		return result;
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public ObjectEntity getObject(long objID) {
		SqlSession sqlSession = this.getSqlSession();
		ObjectEntityDAO dao = sqlSession.getMapper(ObjectEntityDAO.class);
		return dao.selectByPrimaryKey(objID);
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public Collection<ObjectEntity> getObjects(Collection<Long> ObjIds) {
		Collection<ObjectEntity> result = new HashSet<ObjectEntity>();
		Collection<ObjectEntity> entities = getRootObjects();
		for (ObjectEntity entity : entities) {
			if (ObjIds.contains(entity.getObjId())) {
				result.add(entity);
			}
		}
		return result;
	}

	@Override
	public Collection<ObjectEntity> getAllParentObjects(long objId) throws Exception {
		List<ObjectEntity> result = new ArrayList<ObjectEntity>();
		this.searchAllParentObjs(objId, result);
		Collections.reverse(result); // sorted from root Obj id
		return result;
	}

	@Override
	public Collection<ObjectEntity> getAllSubObjects(long objId) {
		List<ObjectEntity> results = new ArrayList<ObjectEntity>();

		Collection<ObjectEntity> allEntities = this.getAllObjects();
		// Search all sub Objs recursively
		searchAllSubObjs(objId, allEntities, results);

		// sort result
		Comparator<ObjectEntity> comparator = new ObjComparator();
		Collections.sort(results, comparator);

		return results;
	}

	@Override
	public Collection<ObjectEntity> getAllSubObjsByType(long objId, String subNeTypeID) {
		List<ObjectEntity> result = new ArrayList<ObjectEntity>();
		Collection<ObjectEntity> allEntities = this.getAllObjects();
		// Search all sub Objs recursively
		searchAllSubObjs(objId, subNeTypeID, allEntities, result);
		return result;
	}

	@Override
	public Map<String, Collection<ObjectEntity>> getTypeGroupedAllSubObjects(long objId) {
		List<ObjectEntity> allSubObjs = new ArrayList<ObjectEntity>();
		Collection<ObjectEntity> allEntities = this.getAllObjects();
		// Search all sub Objs recursively
		searchAllSubObjs(objId, allEntities, allSubObjs);
		// construct result
		Map<String, Collection<ObjectEntity>> result = new HashMap<String, Collection<ObjectEntity>>();

		for (ObjectEntity ne : allSubObjs) {
			Collection<ObjectEntity> sameTypes = result.get(ne.getObjTypeId());
			if (sameTypes == null) {
				sameTypes = new ArrayList<ObjectEntity>();
				result.put(ne.getObjTypeId(), sameTypes);
			}
			sameTypes.add(ne);
		}
		return result;
	}

	@Override
	public Collection<Long> getSubObjectIds(long objId) {
		Collection<Long> results = new ArrayList<Long>();
		Collection<ObjectEntity> allEntities = this.getAllObjects();
		for (ObjectEntity ne : allEntities) {
			if (ne.getParentObjId() == objId) {
				results.add(ne.getObjId());
			}
		}
		return results;
	}

	@Override
	public Collection<Long> getAllSubObjectIds(long objId) {
		Collection<Long> results = new ArrayList<Long>();
		Collection<ObjectEntity> allEntities = this.getAllObjects();
		// Search all sub Obj IDs recursively
		searchAllSubObjIds(objId, allEntities, results);
		return results;
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public long addRootObject(String neName, String neDescr, String neAddr, String neTypeID) throws Exception {
		IDService idService = this.serviceContext.lookup("DSB/IDService");
		long objId = idService.nextID();

		ObjectEntity ne = new ObjectEntity();
		ne.setObjId(objId);

		ne.setObjName(trim(neName));
		ne.setObjAddr(trim(neAddr));
		ne.setObjTypeId(trim(neTypeID));
		ne.setObjIndex(trim(neAddr));
		ne.setParentObjId(EMPTY_ID);
		ne.setRootObjId(objId);
		ne.setObjHierarchy(EMPTY_HIERARCHY);
		ne.setIsActive(false);// default to non-active
		ne.setCreationTime(new Date());
		ne.setLastUpdatedTime(new Date());

		SqlSession sqlSession = this.getSqlSession();
		ObjectEntityDAO dao = sqlSession.getMapper(ObjectEntityDAO.class);
		dao.insert(ne);
		return objId;
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public long addObject(ObjectEntity ne) throws Exception {
		IDService idService = this.serviceContext.lookup("DSB/IDService");
		if (ne.getObjId() <= 0) {
			long objId = idService.nextID();
			ne.setObjId(objId);
		}
		if (ne.getRootObjId() <= 0) {
			ne.setObjHierarchy(EMPTY_HIERARCHY);
			ne.setRootObjId(ne.getObjId());
		}
		if (ne.getParentObjId() <= 0) {
			ne.setParentObjId(EMPTY_ID);
		}

		ne.setObjName(trim(ne.getObjName()));
		ne.setObjAddr(trim(ne.getObjAddr()));
		ne.setObjTypeId(trim(ne.getObjTypeId()));
		ne.setObjIndex(trim(ne.getObjIndex()));

		SqlSession sqlSession = this.getSqlSession();
		ObjectEntityDAO dao = sqlSession.getMapper(ObjectEntityDAO.class);

		dao.insert(ne);

		return ne.getObjId();
	}

	@Override
	@DSBSql(environment = "mysql_dev", batch = true)
	public void deleteObject(long objId) throws Exception {
		Collection<Long> subObjIds = getAllSubObjectIds(objId);
		subObjIds.add(objId);// current Obj should also be deleted
		this.batchDelete(subObjIds); // batch delete
	}

	@Override
	@DSBSql(environment = "mysql_dev", batch = true)
	public void deleteObject(Collection<Long> objIds) throws Exception {
		Collection<ObjectEntity> allObjs = this.getAllObjects();
		Collection<Long> subObjIds = new HashSet<Long>();
		for (Long l : objIds) { // search all sub Objs
			searchAllSubObjIds(l, allObjs, subObjIds);
		}
		subObjIds.addAll(objIds); // current objs will also be deleted.
		this.batchDelete(subObjIds);
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public void updateObject(ObjectEntity ne) throws Exception {
		SqlSession sqlSession = this.getSqlSession();
		ObjectEntityDAO dao = sqlSession.getMapper(ObjectEntityDAO.class);
		ObjectEntity objectEntity = dao.selectByPrimaryKey(ne.getObjId());
		if (objectEntity == null) {
			throw new Exception("Object: [" + ne.getObjName() + "] not found.");
		}
		ne.setLastUpdatedTime(new Date());
		dao.updateByPrimaryKey(ne);
	}

	@Override
	@DSBSql(environment = "mysql_dev", batch = true)
	public void updateObject(Collection<ObjectEntity> records) throws Exception {
		this.batchUpdate(records);
	}

	@Override
	public void refresh() throws Exception {
		close();
	}

	@Override
	public void close() throws Exception {

	}

	private void searchAllSubObjs(long objId, Collection<ObjectEntity> target, Collection<ObjectEntity> result) {
		for (ObjectEntity myne : target) {
			if (myne.getParentObjId() == EMPTY_ID) {
				continue;
			}
			if (myne.getParentObjId() == objId) {
				result.add(myne);
				searchAllSubObjs(myne.getObjId(), target, result);
			}
		}
	}// searchAllSubObjs

	private void searchAllParentObjs(long objId, Collection<ObjectEntity> result) {
		if (objId == EMPTY_ID) {
			return;
		}
		ObjectEntity currentObj = this.getObject(objId);
		if (currentObj == null) {
			return;
		}
		long parentObjId = currentObj.getParentObjId();
		if (parentObjId == EMPTY_ID) {
			return;
		}
		ObjectEntity parentObj = this.getObject(parentObjId);
		if (parentObj == null) {
			return;
		}
		result.add(parentObj);
		searchAllParentObjs(parentObj.getParentObjId(), result);

	}// searchAllSubObjs

	private void searchAllSubObjs(long objId, String typeID, Collection<ObjectEntity> target, Collection<ObjectEntity> result) {
		for (ObjectEntity myne : target) {
			if (myne.getParentObjId() == EMPTY_ID) {
				continue;
			}
			if (myne.getParentObjId() == objId) {
				if (myne.getObjTypeId().equalsIgnoreCase(typeID)) {
					result.add(myne);
				}
				searchAllSubObjs(myne.getObjId(), typeID, target, result);
			}
		}
	}// searchAllSubObjs with type

	private void searchAllSubObjIds(long objId, Collection<ObjectEntity> target, Collection<Long> result) {
		for (ObjectEntity myne : target) {
			if (myne.getParentObjId() == EMPTY_ID) {
				continue;
			}
			if (myne.getParentObjId() == objId) {
				result.add(myne.getObjId());
				searchAllSubObjIds(myne.getObjId(), target, result);
			}
		}
	}// searchAllSubObjs

	@Override
	public void setObjectActiveStatus(Collection<Long> objIds, boolean active) throws Exception {
		// TODO Auto-generated method stub

	}

	@Override
	public Collection<ObjectEntity> getObjectsByType(String typeID) throws Exception {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Collection<Long> getObjectIdsByType(String typeID) throws Exception {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Collection<ObjectEntity> getSubObjects(long objId) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Collection<ObjectEntity> getSubObjects(long objId, String objType) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Map<String, Collection<ObjectEntity>> getTypeGroupedSubObjects(long objId) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void addObject(Collection<ObjectEntity> entities) throws Exception {
		// TODO Auto-generated method stub
		
	}

	/************************ Obj Prop ***********************************/
	@Override
	@DSBSql(environment = "mysql_dev")
	public Collection<ObjectProperty> getPropties(long objId) throws Exception {
		SqlSession sqlSession = this.getSqlSession();
		ObjectPropertyDAO dao = sqlSession.getMapper(ObjectPropertyDAO.class);
		return dao.selectByObjectId(objId);
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public boolean existsProperty(long objId, String propId) {
		SqlSession sqlSession = this.getSqlSession();
		ObjectPropertyDAO dao = sqlSession.getMapper(ObjectPropertyDAO.class);
		ObjectPropertyKey opKey = new ObjectPropertyKey();
		opKey.setObjId(objId);
		opKey.setPropId(propId);
		ObjectProperty objectProperty = dao.selectByPrimaryKey(opKey);
		return objectProperty != null;
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public Collection<ObjectEntity> hasProperty(String propId) throws Exception {
		SqlSession sqlSession = this.getSqlSession();
		ObjectPropertyDAO opDao = sqlSession.getMapper(ObjectPropertyDAO.class);
		Collection<Long> objIds = opDao.selectObjectIds(propId);
		ObjectEntityDAO objDAO = sqlSession.getMapper(ObjectEntityDAO.class);
		return objDAO.getObjsByIds(objIds);
	}

	@Override
	@DSBSql(environment = "mysql_dev")
	public Collection<ObjectEntity> hasProperty(String propId, String propValue) throws Exception {
		SqlSession sqlSession = this.getSqlSession();
		ObjectPropertyDAO opDao = sqlSession.getMapper(ObjectPropertyDAO.class);
		Collection<Long> objIds = opDao.selectObjectIds(propId, propValue);
		ObjectEntityDAO objDAO = sqlSession.getMapper(ObjectEntityDAO.class);
		return objDAO.getObjsByIds(objIds);
	}

	@Override
	public Collection<ObjectProperty> existsProperties(Collection<ObjectProperty> props) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Collection<ObjectProperty> unlockedProperties(Collection<ObjectProperty> props) throws Exception {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public ObjectProperty getProperty(long objId, String propID) throws Exception {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void deleteProperties(Collection<Long> objIds, String propID) throws Exception {
		// TODO Auto-generated method stub

	}

	@Override
	public void deleteProperties(long objId) throws Exception {

	}

	@Override
	public void deleteProperty(long objId, String propID) throws Exception {

	}

	@Override
	public void addProperty(long objId, String propID, String propName, String propValue) throws Exception {
		if (existsProperty(objId, propID)) {
			throw new Exception("Property with objId[" + objId + "] and propID[" + propID + "] already exists!");
		}

	}

	@Override
	public void addProperties(Collection<ObjectProperty> props) throws Exception {

	}

	@Override
	public void saveProperties(Collection<ObjectProperty> props) throws Exception {
	}

	@Override
	public void updateProperty(long objId, String propID, String propName, String propValue) throws Exception {

	}

	@Override
	public void updateProperties(Collection<ObjectProperty> props) throws Exception {

	}

	@Override
	public void lockProperty(long objId, String propID) throws Exception {
	}

	@Override
	public void lockProperties(Collection<ObjectProperty> props) throws Exception {
	}

	@Override
	public void unlockProperty(long objId, String propID) throws Exception {
	}

	@Override
	public void unlockProperties(Collection<ObjectProperty> props) throws Exception {
	}

	private void lock(Collection<ObjectProperty> props, boolean locked) {
	}//

	private boolean isEmpty(String s) {
		return s == null || "".equalsIgnoreCase(s.trim());
	}

	private String trim(String s) {
		if (null != s) {
			return s.trim();
		}
		return "";
	}

	private class ObjComparator implements Comparator<ObjectEntity> {

		@Override
		public int compare(ObjectEntity o1, ObjectEntity o2) {
			int flag = o1.getObjTypeId().compareTo(o2.getObjTypeId());
			if (flag == 0) {
				flag = o1.getObjIndex().compareTo(o2.getObjIndex());
			}
			return flag;
		}
	}

	private void batchInsert(Collection<ObjectEntity> records) throws Exception {
		IDService idService = this.lookup("DSB/IDService");
		SqlSession sqlSession = this.getSqlSession();
		ObjectEntityDAO dao = sqlSession.getMapper(ObjectEntityDAO.class);
		int count = 0;
		int batch_size = 100;
		// pre-process
		for (ObjectEntity record : records) {
			// insert
			Date currentDate = new Date();
			record.setObjId(idService.nextID());
			record.setCreationTime(currentDate);
			record.setLastUpdatedTime(currentDate);
			dao.insert(record);
			count++;
			// 每100条记录提交一次
			if (count == batch_size) {
				// commit
				sqlSession.commit();
				count = 0;
			}
		}
		if (count > 0) {
			// last commit
			sqlSession.commit();
		}
	}

	private void batchUpdate(Collection<ObjectEntity> records) throws Exception {
		SqlSession sqlSession = this.getSqlSession();
		ObjectEntityDAO dao = sqlSession.getMapper(ObjectEntityDAO.class);
		int count = 0;
		int batch_size = 100;
		// pre-process
		for (ObjectEntity record : records) {
			// update
			Date currentDate = new Date();
			record.setLastUpdatedTime(currentDate);
			dao.updateByPrimaryKey(record);
			count++;
			// 每100条记录提交一次
			if (count == batch_size) {
				// commit
				sqlSession.commit();
				count = 0;
			}
		}
		if (count > 0) {
			// last commit
			sqlSession.commit();
		}
	}

	private void batchDelete(Collection<Long> ids) throws Exception {
		SqlSession sqlSession = this.getSqlSession();
		ObjectEntityDAO dao = sqlSession.getMapper(ObjectEntityDAO.class);
		int count = 0;
		int batch_size = 100;
		// pre-process
		for (long id : ids) {
			// delete
			dao.deleteByPrimaryKey(id);
			count++;
			// 每100条记录提交一次
			if (count == batch_size) {
				// commit
				sqlSession.commit();
				count = 0;
			}
		}
		if (count > 0) {
			// last commit
			sqlSession.commit();
		}
	}
}
